/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2022-2022. All rights reserved.
 * Description: image_mbuf_yuv_sender head file
 */
#ifndef IMAGE_MBUF_YUV_SENDER_H
#define IMAGE_MBUF_YUV_SENDER_H

#include "image_sender_base.h"
#include "mdc/cam/camera/cameradecodedmbufserviceinterface_skeleton.h"
#include "util.h"
#include "core/types.h"
#include "driver/ascend_hal.h"
#include "securec.h"
#include "mdc/cam/camera/cameraimagembufserviceinterface_skeleton.h"

#include "camera_data_head.h"

using CamPubImageMbufSkeleton = mdc::cam::camera::skeleton::CameraImageMbufServiceInterfaceSkeleton;
using MbufHandle = uintptr_t;

struct DealMatPara {
    int32_t frameCount;
    int32_t frameSize;
    int32_t seq;
    uint64_t actualLen;
    std::string file;
};

class ImageMbufYuvSender : public ImageSenderBase<CamPubImageMbufSkeleton> {
public:
    ImageMbufYuvSender() = default;
    ImageMbufYuvSender(uint32_t id, cv::Size s, std::string dir) : ImageSenderBase<CamPubImageMbufSkeleton>(id, s, dir)
    {
        sourceYuvPath = dir;
        if (dir.back() != '/') {
            sourceYuvPath += std::string("/") + "yuvSource.yuv";
        } else {
            sourceYuvPath += "yuvSource.yuv";
        }
#ifdef SAVE_SENDER_FILE
        fp_yuv = fopen(sourceYuvPath.c_str(), "wb+");
#endif
    }
    ~ImageMbufYuvSender()
    {
        if (dataSkeleton != nullptr) {
            dataSkeleton->StopOfferService();
            dataSkeleton = nullptr;
        }
        if (handle != nullptr) {
            halBuffDeletePool(handle);
        }
    };

    bool RegisterSender()
    {
        std::cout << "Begin register MBUF image sender." << std::endl;
        auto result = ImageSenderBase<CamPubImageMbufSkeleton>::RegisterSender();
        std::cout << "Finished to register MBUF image sender." << std::endl;
        return result;
    }

    int32_t BufCreatePool(const uint32_t blkSize, const uint32_t blkNum)
    {
        constexpr uint32_t alignSize = 32U;
        mpAttr attr;
        attr.devid = 0;
        attr.mGroupId = 0;
        attr.blkSize = ((blkSize + alignSize) - 1U) & (~(alignSize - 1));
        attr.blkNum = blkNum;
        attr.align = alignSize;
        attr.hugePageFlag = 1U;
        const int32_t ret = halBuffCreatePool(&attr, &handle);
        if (ret != DRV_ERROR_NONE) {
            return -1;
        }
        return 0;
    }

    int32_t BufGetMbuf(poolHandle &handle, uintptr_t &getMbuf) const
    {
        Mbuf *pMbuf = nullptr;
        const int32_t ret = halMbufAllocByPool(handle, &pMbuf);
        if (ret != 0) {
            return -1;
        }
        getMbuf = reinterpret_cast<uintptr_t>(pMbuf);
        return 0;
    }

    int32_t GetMbuf(poolHandle &pHandle, MbufHandle &pMbuf)
    {
        return BufGetMbuf(pHandle, pMbuf);
    }

    bool PackCameraHead(const YuvImageInfo &info, const uintptr_t &head)
    {
        // 获取Mbuf指针及长度
        uintptr_t ptr;
        uint64_t size;
        const auto ret = GetMbufPtrLength(head, ptr, size);
        if (ret != 0) {
            return false;
        }
        const uint64_t headSize = sizeof(mdc::camera::CameraDataHead);
        if (headSize > size) {
            return false;
        }
        const auto pHead = reinterpret_cast<mdc::camera::CameraDataHead *>(ptr);
        if (memset_s(pHead, headSize, 0, headSize) != 0) {
            std::cout << " memset_s fail!" << std::endl;
            return false;
        }
        pHead->seq = info.header.Seq;
        pHead->height = info.height;
        pHead->width = info.width;
        pHead->ebdInfo.expoNumValid = true;
        pHead->ebdInfo.expoNum = 2U; // 2个曝光时间
        pHead->ebdInfo.shutterTimeValid = true;
        pHead->ebdInfo.shutterTime[0U] = info.header.Stamp.Shutter1;
        pHead->ebdInfo.shutterTime[1U] = info.header.Stamp.Shutter2;
        pHead->ebdInfo.expStartTimeStamp.timesec = info.header.Stamp.ExpStartS;
        pHead->ebdInfo.expStartTimeStamp.timensec = info.header.Stamp.ExpStartNs;
        pHead->ebdInfo.expEndTimeStamp.timesec = info.header.Stamp.ExpEndS;
        pHead->ebdInfo.expEndTimeStamp.timensec = info.header.Stamp.ExpEndNs;
        pHead->auxInfo.pubTimeStamp.isValid = true;
        pHead->auxInfo.pubTimeStamp.timesec = info.sendTimeHigh;
        pHead->auxInfo.pubTimeStamp.timensec = info.sendTimeLow;
        pHead->auxInfo.fsyncTimeStamp.isValid = true;
        pHead->auxInfo.fsyncTimeStamp.timesec = info.header.Stamp.Sec;
        pHead->auxInfo.fsyncTimeStamp.timensec = info.header.Stamp.Nsec;
        return true;
    }
    int32_t AppendMbuf(const uintptr_t head, const uintptr_t mbufPtr) const
    {
        return BufChainAppend(head, mbufPtr);
    }

    int32_t BufChainAppend(const uintptr_t head, const uintptr_t mbufPtr) const
    {
        Mbuf * const pHead = reinterpret_cast<Mbuf *>(head);
        Mbuf * const pMbuf = reinterpret_cast<Mbuf *>(mbufPtr);
        const int32_t ret = halMbufChainAppend(pHead, pMbuf);
        if (ret != 0) {
            return -1;
        }
        return 0;
    }

    int32_t GetMbufPtrLength(const MbufHandle &pMbuf, uintptr_t &ptr, uint64_t &len) const
    {
        if (BufGetDataPtr(pMbuf, ptr, len) != 0) {
            return -1;
        }
        return 0;
    }

    int32_t BufGetDataPtr(const uintptr_t in, uintptr_t &out, uint64_t &dataSize) const
    {
        Mbuf * const pSrc = reinterpret_cast<Mbuf *>(in);
        void *pData = nullptr;
        const int32_t ret = halMbufGetDataPtr(pSrc, &pData, &dataSize);
        if (ret != 0) {
            return -1;
        }
        out = reinterpret_cast<uintptr_t>(pData);
        return 0;
    }

    int32_t SetMbufPtr(const MbufHandle &pMbuf, const uint64_t &len) const
    {
        return BufSetDataLen(pMbuf, len);
    }

    int32_t BufSetDataLen(const uintptr_t setMbuf, const uint64_t len) const
    {
        Mbuf * const pMbuf = reinterpret_cast<Mbuf *>(setMbuf);
        const int32_t ret = halMbufSetDataLen(pMbuf, len);
        if (ret != 0) {
            std::cout << "halMbufSetDataLen error: " << ret << std::endl;
            return -1;
        }
        return 0;
    }

    int32_t BufFree(const uintptr_t in) const
    {
        Mbuf * const pSrc = reinterpret_cast<Mbuf *>(in);
        const int32_t ret = halMbufFree(pSrc);
        if (ret != 0) {
            return -1;
        }
        return 0;
    }

    void SaveImage(const ara::core::String &instanceId, const uint32_t &seq, const uintptr_t &ptr,
        const uint32_t &len) const
    {
        FILE *filePtr;
        const auto fileName = instanceId + "_" + std::to_string(seq) + ".yuv";
        filePtr = fopen(fileName.c_str(), "wb");
        if (filePtr != nullptr) {
            (void)fwrite(reinterpret_cast<uint8_t *>(ptr), static_cast<size_t>(len), 1U, filePtr);
            (void)fclose(filePtr);
            filePtr = nullptr;
        } else {
            std::cout << "yuv image save failed file name: " << fileName << std::endl;
        }
    }

    void FillImageAndSend(std::string imgPath, uint32_t seq, int32_t *gStopFlag) override
    {
        (void)gStopFlag;
        std::string path = dir;
        std::string postfix = g_comPara.fileType;
        bool isLoop = g_comPara.loop;
        std::vector<std::string> files;
        int32_t fileCnt = getFilesBySort(path, files, g_comPara.fileType);
        if (fileCnt == 0) {
            return;
        }
        do {
            for (auto &file : files) {
                FILE *yuv_file = fopen(file.c_str(), "rb");
                if (yuv_file == nullptr) {
                    return;
                }
                int32_t frameSize = imgSize.width * imgSize.height * 3 / 2;
                if (fseek(yuv_file, 0, SEEK_END) != 0) {
                    return;
                }
                int32_t fileSize = ftell(yuv_file);
                if (fileSize == -1L) {
                    return;
                }
                rewind(yuv_file);
                int32_t frameCount = fileSize / frameSize;
                dealMatPara.frameCount = frameCount;
                dealMatPara.frameSize = frameSize;
                dealMatPara.seq = seq;
                dealMatPara.file = file;
                bool result = DealMat(yuv_file, file);
                if (!result) {
                    return;
                }
            }
        } while (isLoop);
#ifdef SAVE_SENDER_FILE
        if (fclose(fp_yuv) != 0) {
            std::cout << " fclose(fp_yuv) != 0 " << std::endl;
        }
#endif
    }

private:
    void HandleFrequency()
    {
        if (g_comPara.frequency != "") {
            int32_t freq = atoi(g_comPara.frequency.c_str());
            if (freq != 0) {
                int32_t sleepTime = CN1000000 / freq;
                usleep(sleepTime);
            }
        } else {
            usleep(CN1000000 / 30); // 30hz
        }
    }

    bool DealMat(FILE *yuv_file, std::string &file)
    {
        char *yuvBuff = (char *)malloc(dealMatPara.frameSize * sizeof(char));
        if (yuvBuff == NULL) {
            return false;
        }
        for (int32_t i = 0; i < dealMatPara.frameCount; i++) {
            cv::Mat YUV420NV12;
            YUV420NV12.create(imgSize.height * CN1P5, imgSize.width, CV_8UC1);
            errno_t ret_s = memset_s(yuvBuff, dealMatPara.frameSize * sizeof(char), 0, dealMatPara.frameSize);
            if (ret_s != EOK) {
                return false;
            }
            if (fread(yuvBuff, sizeof(char), dealMatPara.frameSize, yuv_file) != dealMatPara.frameSize) {
                std::cout << "fread yuvBuff error!" << std::endl;
            }
            memcpy_s(YUV420NV12.data, dealMatPara.frameSize, yuvBuff, dealMatPara.frameSize);
            uintptr_t head;
            if (GetMbuf(handle, head) != 0) {
                return false;
            }
            YuvImageInfo yuvImageInfo;
            yuvImageInfo.width = imgSize.width;
            yuvImageInfo.height = imgSize.height;
            // 获取空闲Mbuf模块
            uintptr_t mbufTmp;
            if (GetMbuf(handle, mbufTmp) != 0) {
                return false;
            }
            // 获取mbuff指针及长度
            uintptr_t outStream;
            uint64_t mbufMaxLen;
            uint64_t actualLen;
            if (GetMbufPtrLength(mbufTmp, outStream, mbufMaxLen) != 0) {
                return false;
            }
            actualLen = dealMatPara.frameSize;
            dealMatPara.actualLen = actualLen;
            if (memcpy_s(reinterpret_cast<uint8_t *>(outStream), mbufMaxLen, YUV420NV12.data, actualLen) != 0) {
                std::cout << "outStream memcpy_s failed" << std::endl;
            }
            int32_t result = DealMbufPtr(mbufTmp, YUV420NV12, yuvImageInfo, head);
            if (!result) {
                return false;
            }
        }
        free(yuvBuff);
        return true;
    }

    bool DealMbufPtr(uintptr_t &mbufTmp, cv::Mat &YUV420NV12, YuvImageInfo &yuvImageInfo, uintptr_t &head)
    {
        int ret;
        // 设置mbuf长度
        ret = SetMbufPtr(mbufTmp, dealMatPara.actualLen);
        if (ret != 0) {
            BufFree(mbufTmp);
            return false;
        }
        // 设置mbuf
        yuvImageInfo.mbuffer = mbufTmp;
        yuvImageInfo.length = static_cast<uint32_t>(dealMatPara.actualLen);
        if (!PackCameraHead(yuvImageInfo, head)) {
            return false;
        }
        if (dataSkeleton == nullptr || dataSkeleton == NULL) {
            return false;
        }
        auto apData = dataSkeleton->cameraImageMbufEvent.Allocate();
        const uint32_t imageHeadType_ = 1U;
        const uint32_t imageYuvSemiplanarType_ = 1001U;
        const uint32_t mutiImageVersion_ = 0x1000U;
        apData->type.push_back(imageHeadType_);
        ret = AppendMbuf(head, yuvImageInfo.mbuffer);
        if (ret != 0) {
            return false;
        }
        apData->type.push_back(imageYuvSemiplanarType_);
        apData->version = mutiImageVersion_;
        apData->seq = dealMatPara.seq;
        apData->head = reinterpret_cast<rawBuffer *>(head);
#ifdef SAVE_SENDER_FILE
        if (fwrite(YUV420NV12.data, 1, dealMatPara.frameSize, fp_yuv) != dealMatPara.frameSize) {
            std::cout << "fwrite() != frameSize" << std::endl;
        }
#endif
        dataSkeleton->cameraImageMbufEvent.Send(std::move(apData));
        dealMatPara.seq++;
        HandleFrequency();
        return true;
    }

private:
    poolHandle handle {};
    float lastTime { 0.0F };
    std::string sourceYuvPath;
    FILE *fp_yuv;
    DealMatPara dealMatPara;
};

#endif // IMAGE_MBUF_YUV_SENDER_H
